Borland Online And The Cobb Group Present:


March, 1994 - Vol. 1 No. 3

4.0 Debugging Technique - Adding the new diagnostic macros to your code

One of the most significant problems most programmers face during application development is debugging the code. As a result, Borland C++ supplies a strong set of debugging tools to help locate code problems.

Borland C++ 4.0 provides two new diagnostic macros that can help you embed diagnostic messages and conditional messages in your functions. In this article, we'll show you how to add these new macros to your code and how to turn them on and off when you compile the application.

The Problem

Many programmers like to embed debugging or diagnostic code directly in their programs. Some programmers do this because they're unfamiliar with Borland's debugging tools. Others embed diagnostic code so they can analyze complex bugs in the true native environment for the application.

Unfortunately, if you add diagnostic code to a specific function, it may significantly reduce the speed of the function's execution. Due to speed considerations, you may have to remove the diagnostic code after you debug the production version of your application, only to reenter the code when a new bug appears.

For some time now, programmers have been surrounding diagnostic code with conditional-compile statements. These usually take the form

#ifdef DIAGNOSTIC
if(param == 0)
  cout << "Error at foo(), param == 0!";
#endif

This way, if you add a #define statement for the label DIAGNOSTIC early in the file, the compiler will see and process the error-checking code. If you remove the #define statement for DIAGNOSTIC, the preprocessor skips to the #endif statement and the compiler never sees the code.

However, adding conditional-compile statements can be a very tedious process. If you don't add the #define statement at the correct location in the file, or if you misspell the label, the preprocessor may skip over the diagnostic code for an important function.

Finally, if you decide to send your diagnostic messages to a file instead of using the standard output stream cout or the printf() library function, you'll have to change each line of diagnostic code individually. Obviously, this isn't practical for a large application. Fortunately, Borland solves this problem with the two new macros TRACE and WARN.

Using the New Macros

Borland C++ 4.0 adds two new diagnostic macros­­TRACE and WARN­­to conditionally display messages and warnings at runtime. The CHECKS.H header file defines these macros.

To use those macros in your code, add a #include <checks.h> statement to your source files. Be sure to add this statement ahead of any functions that will use the diagnostic macros.

To enable the TRACE macro, be sure the label __TRACE appears in a #define statement. To enable the WARN macro, be sure the label __WARN appears in a separate #define statement. You can add these statements explicitly to your source files, or you can add the labels to the Defines list in the compiler's Options dialog box for that particular file.

The TRACE macro allows you to display simple diagnostic messages with the syntax

TRACE("Diagnostic Text");

Even in this form, the macro is an improvement over creating messages by hand. However, because the macro uses a member of the class strstream to format the output text, you can also write

TRACE("Diagnostic status " << statusCode);

to display the value of a variable. Here, we show a variable with the name statusCode, but you can use any variable you can send to an output stream.

You can even use streamable variables, or expressions that result in streamable variables, as the only parameters to the TRACE macro to avoid passing any text. However, displaying a value without a description may cause readability problems.

The WARN macro is similar, but it allows you to display a warning message based on a condition. With this macro, you can write

WARN((code > 3), "Error, run Will 
		Robinson");

to display the warning text only when the first parameter isn't zero.

As with the TRACE macro, the WARN macro formats its text with a member of the class strstream. This allows you to display the value of variables based on the macro's conditional parameter by writing

WARN((code > 3), "Error, status == " 
		<< status);

Finally, since these macros send their output to the standard output stream cout, you can easily redirect the data to another output stream. Now, let's enter an example program that uses the WARN and TRACE macros.

Writing an application with Conditional Diagnostics

Launch the Borland C++ 4.0 IDE. When the IDE main window appears, choose New Project... from the Project menu.

When the New Project dialog box appears, enter

c:\bc4\diag\diag.ide

in the Project Path and Name entry field. In the Target Type section, select Application [.exe] in the list box and choose DOS Standard from the Platform combo box.

When you finish, the New Project dialog box should resemble the one shown in Figure A. Click the OK button to create the DIAG.IDE project.


Figure A - You'll use the New Project dialog box to begin the example application.

Open the DIAG.CPP source file by double-clicking on its icon in the Project window. When DIAG.CPP's editor window appears, enter the source code from Listing A.


Listing A: DIAG.CPP

#define __TRACE
#include <checks.h>
#include <fstream.h>
ofstream traceFile("trace.txt");

int foo(int param)
{  
  TRACE("Entering foo");
  WARN(param, "param == " << param << "!");
  TRACE("Returning " << param << " from foo");
  return param;
}

int main()
{
  if(traceFile) cout = traceFile;
  TRACE( "Entering Main" );
  foo(5);
  TRACE( "Leaving Main" );
  return 0;
}

The first line is a #define statement for the __TRACE label, which enables the TRACE macro. Later, when we compile this program, we'll provide a #define statement by changing the compiler options for this file.

The next two lines in the program are #include statements that tell the preprocessor to embed the contents of CHECKS.H and FSTREAM.H in this file. The file CHECKS.H defines the diagnostic macros, and the file FSTREAM.H declares the ofstream class we'll use to create an output file stream for an error file.

Next, we declare a global ofstream object for the TRACE.TXT file. Later, we'll redirect the standard output file cout to send its output to this file.

The foo() function uses both the TRACE and the WARN macros to output text and the value of a variable. The main() function that follows calls the diagnostic macros with output text only.

At the very beginning of the main() function, you'll find the line

if(traceFile) cout = traceFile;

This statement checks to see if the output file stream traceFile is valid (no errors opening the file).

If the file is valid, we then assign the output file stream to the standard output stream cout to redirect its output to the file TRACE.TXT. Now, let's try our example.

Running The Example

In the Project window, right-click on the DIAG.CPP file's name. Now, choose Edit Local Options... from the pop-up menu.

When the Options: At DIAG.CPP dialog box appears, double-click on Compiler in the Topics list box. Then select Defines from the the same Topics list box and enter __WARN; in the Defines entry field. Click the OK button to save these settings.

Now, compile and run DIAG.EXE by double-clicking on its name in the Project window. When the IDE finishes compiling and linking the program, the screen will go black momentarily and then return to normal. (The screen goes black when the IDE runs this application as a DOS program under Windows. Because this program doesn't do anything except write some diagnostic messages to a file, it runs and then returns quickly.)

To see the output file, choose Open... from the IDE's File menu and then enter TRACE.TXT in the File Name entry field. When you click the OK button, an editor window for the file will appear, as shown in Figure B.

Figure B - The error file TRACE.TXT contains the output of the diagnostic macros.

To remove the warning message code from DIAG.EXE, just remove the __WARN label from the DIAG.CPP file's compiler options. To remove the trace message code, remove the #define __TRACE statement at the beginning of the DIAG.CPP file.

By the way, there's nothing special about putting the #define __TRACE statement in the file and putting the __WARN label in the compiler options. You can put a #define __WARN statement in the source file and the __TRACE label in the compiler options, or you can put both in either location.

Conclusion

The TRACE and WARN macros give you more control over conditional-compile diagnostic messages than the #ifdef statements some programmers use. By taking advantage of these macros, you can develop the habit of leaving diagnostic code in your application without worrying about removing it from your production code.

Return to the Borland C++ Developer's Journal index

Subscribe to the Borland C++ Developer's Journal


Copyright (c) 1996 The Cobb Group, a division of Ziff-Davis Publishing Company. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis Publishing Company is prohibited. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Publishing Company.